﻿//---------------------------------------------------------------------
// Author: Reinhard Lehrbaum
//
// Description: Manipulation from the properties of a DirectoryEntry.
//
// Creation Date: 2007-01-12
//---------------------------------------------------------------------

using ActiveDs;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.DirectoryServices;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;

namespace Pscx.Providers
{
    class DirectoryEntryProperty
    {
        public enum DirectoryEntryClass
        {
            Unkown,
            User,
            Group,
            Contact,
            Computer,
            OrganizationalUnit,
            Volume,
            Printer,
            msMQRecipient,
            inetOrgPerson
        }

        public enum PropertyAccess
        {
            Read,
            Write,
            ReadWrite
        }

        public enum PropertyWrite
        {
            Value,
            Add,
            Remove,
            Invoke
        }

        private DirectoryEntry _entry;
        private DirectoryEntryClass _entryClass = DirectoryEntryClass.Unkown;
        private string _entryClassName = "";

        private bool _useAdditionalProperties = true;
        private bool _showAllProperties = false;
        private PropertyWrite _writeType = PropertyWrite.Value;

        private bool _returnHex = true;

        private Dictionary<string, PropertyAccess> _additionalProperties;
        private ArrayList _allPropertyNames = null;

        public DirectoryEntryProperty(DirectoryEntry entry)
        {
            _entry = entry;
            _entry.RefreshCache(new string[] { "canonicalName", "primaryGroupToken", "modifyTimeStamp", "createTimeStamp" });

            _additionalProperties = new Dictionary<string, PropertyAccess>(StringComparer.CurrentCultureIgnoreCase);

            GetEntryClass();
            GetAdditionalProperties();
        }

        public object this[string PropertyName]
        {
            get
            {
                return GetDirectoryEntryProperty(PropertyName);
            }
            set
            {
                SetDirectoryEntryProperty(PropertyName, value);
            }
        }

        #region Parameter
        public bool UseAdditionalProperties
        {
            get { return _useAdditionalProperties; }
            set { _useAdditionalProperties = value; }
        }

        public bool ShowAllProperties
        {
            get { return _showAllProperties; }
            set { _showAllProperties = value; }
        }

        public PropertyWrite WriteType
        {
            get { return _writeType; }
            set { _writeType = value; }
        }
        #endregion

        private ArrayList AllPropertyNames
        {
            get
            {
                if (_allPropertyNames == null)
                {
                    _allPropertyNames = new ArrayList();
                    _allPropertyNames = GetPropertyNameList(true);
                }
                return _allPropertyNames;
            }
        }

        private void GetAdditionalProperties()
        {
            _additionalProperties.Clear();

            switch (_entryClass)
            {
                case DirectoryEntryClass.inetOrgPerson:
                case DirectoryEntryClass.User:
                    //Methods
                    _additionalProperties.Add("Password", PropertyAccess.Write);

                    //** Account
                    _additionalProperties.Add("AccountLocked", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("NoPassordChange", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("DontExpirePassword", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("EncryptedTextPasswordAllowed", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("AccountDisable", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("SmartcardRequired", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("NotDelegated", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("UseDESKeyOnly", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("DontRequirePreauth", PropertyAccess.ReadWrite);

                    //Terminal Services Attributes
                    //** Remote Control
                    _additionalProperties.Add("EnableRemoteControl", PropertyAccess.ReadWrite);
                    //** Terminal Services Profile
                    _additionalProperties.Add("TerminalServicesProfilePath", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("TerminalServicesHomeDirectory", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("TerminalServicesHomeDrive", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("AllowLogon", PropertyAccess.ReadWrite);
                    //** Sessions
                    _additionalProperties.Add("MaxDisconnectionTime", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("MaxConnectionTime", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("MaxIdleTime", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("BrokenConnectionAction", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("ReconnectionAction", PropertyAccess.ReadWrite);
                    //** Environment
                    _additionalProperties.Add("TerminalServicesInitialProgram", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("TerminalServicesWorkDirectory", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("ConnectClientDrivesAtLogon", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("ConnectClientPrintersAtLogon", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("DefaultToMainPrinter", PropertyAccess.ReadWrite);

                    //** COM+ 
                    //_additionalProperties.Add("msCOM-UserPartitionSetLink", 

                    //** Member Of
                    _additionalProperties.Add("PrimaryGroup", PropertyAccess.ReadWrite);

                    break;
                case DirectoryEntryClass.Group:
                    //** General
                    _additionalProperties.Add("Group", PropertyAccess.ReadWrite);
                    break;
                case DirectoryEntryClass.Contact:
                    break;
                case DirectoryEntryClass.Computer:
                    //** Member Of
                    _additionalProperties.Add("PrimaryGroup", PropertyAccess.ReadWrite);

                    //** Delegation
                    _additionalProperties.Add("TrustedForDelegation", PropertyAccess.ReadWrite);
                    _additionalProperties.Add("TrustedToAuthenticateForDelegation", PropertyAccess.ReadWrite);
                    break;
                case DirectoryEntryClass.OrganizationalUnit:
                    break;
                case DirectoryEntryClass.Volume:
                    break;
                case DirectoryEntryClass.Printer:
                    break;
                case DirectoryEntryClass.msMQRecipient:
                    break;
                case DirectoryEntryClass.Unkown:
                    break;
                default:
                    break;
            }
        }

        private void GetEntryClass()
        {
            _entryClass = DirectoryEntryClass.Unkown;
            _entryClassName = "";

            ArrayList ClassList = new ArrayList();

            if (_entry.Properties["objectclass"].Value.GetType() == typeof(object[]))
            {
                ClassList.AddRange((object[])_entry.Properties["objectclass"].Value);
            }
            else
            {
                ClassList.Add(_entry.Properties["objectclass"].Value);
            }
            if (ClassList.Count > 0)
            {
                _entryClassName = ClassList[ClassList.Count - 1].ToString();

                switch (_entryClassName)
                {
                    case "organizationalUnit":
                        _entryClass = DirectoryEntryClass.OrganizationalUnit;
                        break;
                    case "inetOrgPerson":
                        _entryClass = DirectoryEntryClass.inetOrgPerson;
                        break;
                    case "user":
                        _entryClass = DirectoryEntryClass.User;
                        break;
                    case "computer":
                        _entryClass = DirectoryEntryClass.Computer;
                        break;
                    case "group":
                        _entryClass = DirectoryEntryClass.Group;
                        break;
                    case "contact":
                        _entryClass = DirectoryEntryClass.Contact;
                        break;
                    case "msMQ-Custom-Recipient":
                        _entryClass = DirectoryEntryClass.msMQRecipient;
                        break;
                    case "volume":
                        _entryClass = DirectoryEntryClass.Volume;
                        break;
                    case "printQueue":
                        _entryClass = DirectoryEntryClass.Printer;
                        break;
                    default:
                        _entryClass = DirectoryEntryClass.Unkown;
                        break;
                }
            }
        }

        public ArrayList GetPropertyNameList()
        {
            return GetPropertyNameList(_showAllProperties);
        }

        private ArrayList GetPropertyNameList(bool ShowAll)
        {
            ArrayList result = new ArrayList();

            if (!ShowAll)
            {
                foreach (string propertyName in _entry.Properties.PropertyNames)
                {
                    result.Add(propertyName);
                }
            }
            else
            {
                using (DirectoryEntry schema = _entry.SchemaEntry)
                {
                    Type t = schema.NativeObject.GetType();

                    object optional = t.InvokeMember(
                        "OptionalProperties",
                        BindingFlags.Public | BindingFlags.GetProperty,
                        null,
                        schema.NativeObject,
                        null
                        );

                    if (optional is ICollection)
                    {
                        foreach (string s in ((ICollection)optional))
                        {
                            if (!_additionalProperties.ContainsKey(s))
                            {
                                result.Add(s);
                            }
                        }
                    }

                    object mand = t.InvokeMember(
                        "MandatoryProperties",
                        BindingFlags.Public | BindingFlags.GetProperty,
                        null,
                        schema.NativeObject,
                        null
                        );

                    if (mand is ICollection)
                    {
                        foreach (string s in ((ICollection)mand))
                        {
                            if (_additionalProperties.ContainsKey(s))
                            {
                                result.Add(s);
                            }
                        }
                    }
                }
            }

            if (_useAdditionalProperties)
            {
                foreach (KeyValuePair<string, PropertyAccess> KeyItem in _additionalProperties)
                {
                    result.Add(KeyItem.Key);
                }
            }

            result.Sort();

            return result;
        }

        private object GetDirectoryEntryProperty(string Name)
        {
            object result = null;
            PropertyAccess access = PropertyAccess.Write;

            try
            {
                if (_additionalProperties.TryGetValue(Name, out access))
                {
                    if (access == PropertyAccess.Read || access == PropertyAccess.ReadWrite)
                    {
                        result = GetProperty(Name);
                    }
                    else
                    {
                        //result = "<WriteOnly>";
                    }
                }
                else
                {
                    PropertyValueCollection propertyValue = _entry.Properties[Name];

                    Type ValueType = propertyValue.Value.GetType();
                    if (ValueType == typeof(object[]))
                    {
                        ArrayList list = new ArrayList();
                        foreach (string ValueItem in (object[])propertyValue.Value)
                        {
                            list.Add(ValueItem);
                        }
                        result = (object[])list.ToArray();
                    }
                    else if (ValueType == typeof(byte[]))
                    {
                        ArrayList list = new ArrayList();
                        foreach (byte ValueItem in (byte[])propertyValue.Value)
                        {
                            if (_returnHex)
                            {
                                list.Add("0x" + ValueItem.ToString("x2"));
                            }
                            else
                            {
                                list.Add(ValueItem);
                            }
                        }
                        result = (object[])list.ToArray();
                    }
                    else if (ValueType.FullName.Equals("System.__ComObject"))
                    {
                        if ((propertyValue.Value as IADsLargeInteger) != null)
                        {
                            IADsLargeInteger largeInt = (IADsLargeInteger)propertyValue.Value;
                            try
                            {
                                result = DateTime.FromFileTime((long)largeInt.HighPart << 32 | (uint)largeInt.LowPart);
                            }
                            catch { }
                        }
                        else if ((propertyValue.Value as IADsSecurityDescriptor) != null)
                        {
                            //IADsSecurityDescriptor secDesc = (IADsSecurityDescriptor)propertyValue.Value;
                            //IADsAccessControlList dacl = (IADsAccessControlList)secDesc.DiscretionaryAcl;

                            //ArrayList list = new ArrayList();
                            //foreach (IADsAccessControlEntry ace in dacl)
                            //{
                            //    list.Add(string.Format("{0}:{1}:{2}:{3}:{4}:{5}",
                            //        ace.Trustee,
                            //        ace.AccessMask,
                            //        ace.AceType,
                            //        ace.AceFlags,
                            //        ace.Flags,
                            //        ace.ObjectType));
                            //}
                            //result = (object[])list.ToArray();
                            result = null;
                        }
                        else if ((propertyValue.Value as IADsDNWithBinary) != null)
                        {
                            IADsDNWithBinary dnWithBinary = (IADsDNWithBinary)propertyValue.Value;
                            result = dnWithBinary.DNString + ":" + dnWithBinary.BinaryValue.ToString();
                        }
                        else if ((propertyValue.Value as IADsDNWithString) != null)
                        {
                            IADsDNWithString dnWithString = (IADsDNWithString)propertyValue.Value;
                            result = dnWithString.DNString + ":" + dnWithString.StringValue;
                        }
                        else
                        {
                            //throw new System.ArgumentException("Unknown COM Object type.");
                            //result = "<Unknown COM Object type>";
                            result = propertyValue.Value;
                        }
                    }
                    else
                    {
                        result = propertyValue.Value.ToString();
                    }
                }
            }
            catch (Exception ex)
            {
                if (AllPropertyNames.IndexOf(Name) < 0)
                {
                    throw ex;
                }
            }

            return result;
        }

        private void SetDirectoryEntryProperty(string Name, object Value)
        {
            PropertyAccess access = PropertyAccess.Read;
            if (_additionalProperties.TryGetValue(Name, out access))
            {
                if (access == PropertyAccess.Write || access == PropertyAccess.ReadWrite)
                {
                    SetProperty(Name, Value);
                }
                else
                {
                    throw new Exception(string.Format("Property '{0}' is readonly.", Name));
                }
            }
            else
            {
                object writeValue = Value;
                PropertyWrite write = _writeType;

                switch (GetPropertySyntax(Name))
                {
                    case "INTEGER8":
                        IADsLargeInteger largeInt= (IADsLargeInteger) new LargeInteger();
                        if (Value.ToString().Equals("-1"))
                        {
                            largeInt.HighPart = Int32.MaxValue;
                            largeInt.LowPart = -1;
                        }
                        else
                        {
                            DateTime accountExpires = DateTime.Parse(Value.ToString());
                            long i64 = accountExpires.ToFileTime();
                            largeInt.HighPart = (int)(i64 >> 32);
                            largeInt.LowPart = (int)(i64 & 0xFFFFFFFF);
                        }
                        writeValue = largeInt;
                        break;
                    case "ObjectSecurityDescriptor":
                        throw new Exception("Property not implemented");
                    //break;
                    case "OctetString":
                        writeValue = ConvertToOctetString(Value);
                        break;
                    case "INTEGER":
                        writeValue = Int32.Parse(Value.ToString());
                        break;
                    default:
                        break;
                }

                switch (write)
                {
                    case PropertyWrite.Value:
                        _entry.Properties[Name].Value = writeValue;
                        break;
                    case PropertyWrite.Add:
                        _entry.Properties[Name].Add(writeValue);
                        break;
                    case PropertyWrite.Remove:
                        _entry.Properties[Name].Remove(writeValue);
                        break;
                    case PropertyWrite.Invoke:
                        _entry.Invoke(Name, new object[] { writeValue });
                        break;
                    default:
                        break;
                }
                _entry.CommitChanges();
            }
        }

        public void ClearProperty(string Name)
        {
            PropertyAccess access = PropertyAccess.Read;
            if (!_additionalProperties.TryGetValue(Name, out access))
            {
                _entry.Properties[Name].Clear();
                _entry.CommitChanges();
            }
        }

        #region GET

        private object GetProperty(string Name)
        {
            object result = null;

            switch (Name.ToLower())
            {
                case "accountdisable": //AccountDisable
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_ACCOUNTDISABLE);
                    break;
                case "dontexpirepassword": //DontExpirePassword
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_DONT_EXPIRE_PASSWD);
                    break;
                case "accountlocked": //AccountLocked
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_LOCKOUT);
                    break;
                case "dontrequirepreauth": //DontRequirePreauth
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_DONT_REQUIRE_PREAUTH);
                    break;
                case "encryptedtextpasswordallowed": //EncryptedTextPasswordAllowed
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED);
                    break;
                case "notdelegated": //NotDelegated
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_NOT_DELEGATED);
                    break;
                case "smartcardrequired": //SmartcardRequired
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_SMARTCARD_REQUIRED);
                    break;
                case "usedeskeyonly": //UseDESKeyOnly
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_USE_DES_KEY_ONLY);
                    break;
                case "nopassordchange": //NoPassordChange
                    result = GetUserCannotChangePassword();
                    break;

                case "enableremotecontrol": //EnableRemoteControl
                case "terminalservicesprofilepath": //TerminalServicesProfilePath
                case "terminalserviceshomedirectory": //TerminalServicesHomeDirectory
                case "terminalserviceshomedrive": //TerminalServicesHomeDrive
                case "allowlogon": //AllowLogon
                case "maxdisconnectiontime": //MaxDisconnectionTime
                case "maxconnectiontime": //MaxConnectionTime
                case "maxidletime": //MaxIdleTime
                case "brokenconnectionaction": //BrokenconnectionAction
                case "reconnectionaction": //ReconnectionAction
                case "terminalservicesinitialprogram": //TerminalServicesInitialProgram
                case "terminalservicesworkdirectory": //TerminalServicesWorkDirectory
                case "connectclientdrivesatlogon": //ConnectClientDrivesAtLogon
                case "connectclientprintersatlogon": //ConnectClientPrintersAtLogon
                case "defaulttomainprinter": //DefaultToMainPrinter
                    try
                    {
                        result = _entry.InvokeGet(Name);
                    }
                    catch { }
                    break;
                case "primarygroup": //PrimaryGroup
                    result = GetPrimaryGroup();
                    break;

                case "trustedfordelegation": //TrustedForDelegation
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_TRUSTED_FOR_DELEGATION);
                    break;
                case "trustedtoauthenticatefordelegation": //TrustedToAuthenticateForDelegation
                    result = GetUserFlag(ADS_USER_FLAG.ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION);
                    break;

                case "group": //Group
                    result = new object[] { GetGroupScope(), GetGroupType() };
                    break;
                default:
                    throw new Exception("Property not supported (" + Name + ")");
            }

            return result;
        }

        private bool GetUserFlag(ADS_USER_FLAG flag)
        {
            int userAccountControl = (int)_entry.Properties["userAccountControl"].Value;
            return (userAccountControl & (int)flag) == (int)flag;
        }

        private bool GetUserCannotChangePassword()
        {
            const string CHANGE_PASSWORD_GUID = "{AB721A53-1E2F-11D0-9819-00AA0040529B}";

            IADsSecurityDescriptor secDes = (IADsSecurityDescriptor)_entry.Properties["ntSecurityDescriptor"].Value;
            IADsAccessControlList dacl = (IADsAccessControlList)secDes.DiscretionaryAcl;
            bool foundEveryone = false;
            bool foundSelf = false;
            string AccountSelf = GetWellKnownAccountName(WellKnownSidType.SelfSid);
            string AccoungEveryone = GetWellKnownAccountName(WellKnownSidType.WorldSid);

            foreach (IADsAccessControlEntry ace in dacl)
            {
                if (!string.IsNullOrEmpty(ace.ObjectType) && ace.ObjectType.Equals(CHANGE_PASSWORD_GUID))
                {
                    if (ace.Trustee.Equals(AccoungEveryone) && ace.AceType == (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT)
                    {
                        foundEveryone = true;
                    }
                    if (ace.Trustee.Equals(AccountSelf) && ace.AceType == (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT)
                    {
                        foundSelf = true;
                    }
                }
            }
            return foundEveryone && foundSelf;
        }

        private string GetPrimaryGroup()
        {
            DirectoryEntry root = _entry;
            while (root != null && !root.Name.ToUpper().StartsWith("DC="))
            {
                root = root.Parent;
            }

            int primaryGroupID = (int)_entry.Properties["primaryGroupID"].Value;
            byte[] objectSid = (byte[])_entry.Properties["objectSid"].Value;
            System.Text.StringBuilder escapedGroupSid = new System.Text.StringBuilder();
            //Copy over everything but the last four bytes(sub-authority) 
            //Doing so gives us the RID of the domain 
            for (uint i = 0; i < objectSid.Length - 4; i++)
            {
                escapedGroupSid.AppendFormat("\\{0:x2}", objectSid[i]);
            }

            //Add the primaryGroupID to the escape string to build the SID of the primaryGroup 
            for (uint i = 0; i < 4; i++)
            {
                escapedGroupSid.AppendFormat("\\{0:x2}", (primaryGroupID & 0xFF));
                primaryGroupID >>= 8;
            }

            //Search the directory for a group with this SID 
            DirectorySearcher searcher = new DirectorySearcher(root);
            searcher.Filter = "(&(objectCategory=Group)(objectSID=" + escapedGroupSid.ToString() + "))";
            searcher.PropertiesToLoad.Add("distinguishedName");
            return (string)searcher.FindOne().Properties["distinguishedName"][0];
        }

        private int GetGroupScope()
        {
            int scope = -1;
            int GroupType = (int)_entry.Properties["groupType"].Value;

            if ((GroupType & (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_LOCAL_GROUP) == (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_LOCAL_GROUP)
            {
                scope = 0;
            }
            else if ((GroupType & (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_GLOBAL_GROUP) == (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_GLOBAL_GROUP)
            {
                scope = 1;
            }
            else if ((GroupType & (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_UNIVERSAL_GROUP) == (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_UNIVERSAL_GROUP)
            {
                scope = 2;
            }

            return scope;
        }

        private int GetGroupType()
        {
            int type = -1;
            int GroupType = (int)_entry.Properties["groupType"].Value;

            if ((GroupType & (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_SECURITY_ENABLED) == (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_SECURITY_ENABLED)
            {
                type = 0;
            }
            else
            {
                type = 1;
            }

            return type;
        }

        #endregion

        #region SET

        private void SetProperty(string Name, object Value)
        {
            switch (Name.ToLower())
            {
                case "accountdisable": //AccountDisable
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_ACCOUNTDISABLE, (bool)Value);
                    break;
                case "dontexpirepassword": //DontExpirePassword
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_DONT_EXPIRE_PASSWD, (bool)Value);
                    break;
                case "accountlocked": //AccountLocked
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_LOCKOUT, (bool)Value);
                    break;
                case "dontrequirepreauth": //DontRequirePreauth
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_DONT_REQUIRE_PREAUTH, (bool)Value);
                    break;
                case "encryptedtextpasswordallowed": //EncryptedTextPasswordAllowed
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, (bool)Value);
                    break;
                case "notdelegated": //NotDelegated
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_NOT_DELEGATED, (bool)Value);
                    break;
                case "smartcardrequired": //SmartcardRequired
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_SMARTCARD_REQUIRED, (bool)Value);
                    break;
                case "usedeskeyonly": //UseDESKeyOnly
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_USE_DES_KEY_ONLY, (bool)Value);
                    break;
                case "nopassordchange": //NoPassordChange
                    SetUserCannotChangePassword(!(bool)Value);
                    break;

                case "enableremotecontrol": //EnableRemoteControl
                case "terminalservicesprofilepath": //TerminalServicesProfilePath
                case "terminalserviceshomedirectory": //TerminalServicesHomeDirectory
                case "terminalserviceshomedrive": //TerminalServicesHomeDrive
                case "allowlogon": //AllowLogon
                case "maxdisconnectiontime": //MaxDisconnectionTime
                case "maxconnectiontime": //MaxConnectionTime
                case "maxidletime": //MaxIdleTime
                case "brokenconnectionaction": //BrokenconnectionAction
                case "reconnectionaction": //ReconnectionAction
                case "terminalservicesinitialprogram": //TerminalServicesInitialProgram
                case "terminalservicesworkdirectory": //TerminalServicesWorkDirectory
                case "connectclientdrivesatlogon": //ConnectClientDrivesAtLogon
                case "connectclientprintersatlogon": //ConnectClientPrintersAtLogon
                case "defaulttomainprinter": //DefaultToMainPrinter
                    _entry.InvokeSet(Name, Value.ToString());
                    break;
                case "primarygroup": //PrimaryGroup
                    SetPrimaryGroup(Value.ToString());
                    break;
                case "password": //Password
                    _entry.Invoke("SetPassword", new object[] { Value.ToString() });
                    break;

                case "trustedfordelegation": //TrustedForDelegation
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_TRUSTED_FOR_DELEGATION, (bool)Value);
                    break;
                case "trustedtoauthenticatefordelegation": //TrustedToAuthenticateForDelegation
                    SetUserFlag(ADS_USER_FLAG.ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, (bool)Value);
                    break;

                case "group": //Group
                    SetGroup((object[])Value);
                    break;

                default:
                    throw new Exception("Property not supported (" + Name + ")");
            }
            _entry.CommitChanges();
        }

        private void SetUserFlag(ADS_USER_FLAG flag, bool value)
        {
            int userAccountControl = (int)_entry.Properties["userAccountControl"].Value;

            if (value)
            {
                userAccountControl |= (int)flag;
            }
            else
            {
                userAccountControl &= ~(int)flag;
            }

            _entry.Properties["userAccountControl"].Value = userAccountControl;
        }

        private void SetUserCannotChangePassword(bool AllowChange)
        {
            const string CHANGE_PASSWORD_GUID = "{AB721A53-1E2F-11D0-9819-00AA0040529B}";
            string NameEveryone = GetWellKnownAccountName(WellKnownSidType.WorldSid);
            string NameSelf = GetWellKnownAccountName(WellKnownSidType.SelfSid);
            int WorkFlag = AllowChange ? (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_ALLOWED_OBJECT : (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT;

            IADsSecurityDescriptor secDes = (IADsSecurityDescriptor)_entry.Properties["ntSecurityDescriptor"].Value;
            IADsAccessControlList dacl = (IADsAccessControlList)secDes.DiscretionaryAcl;

            bool EditEveryone = false;
            bool EditSelf = false;

            foreach (IADsAccessControlEntry ace in dacl)
            {
                if (!string.IsNullOrEmpty(ace.ObjectType) && ace.ObjectType.Equals(CHANGE_PASSWORD_GUID))
                {
                    if (ace.Trustee.Equals(NameEveryone))
                    {
                        ace.AceType = WorkFlag;
                        EditEveryone = true;
                    }

                    if (ace.Trustee.Equals(NameSelf))
                    {
                        ace.AceType = WorkFlag;
                        EditSelf = true;
                    }
                }
            }
            if (!EditEveryone)
            {
                IADsAccessControlEntry aceEveryone = new ActiveDs.AccessControlEntry();
                aceEveryone.AccessMask = (int)ADS_RIGHTS_ENUM.ADS_RIGHT_DS_CONTROL_ACCESS;
                aceEveryone.AceType = WorkFlag;
                aceEveryone.AceFlags = 0;
                aceEveryone.Flags = (int)ADS_FLAGTYPE_ENUM.ADS_FLAG_OBJECT_TYPE_PRESENT;
                aceEveryone.ObjectType = CHANGE_PASSWORD_GUID;
                aceEveryone.Trustee = NameEveryone;
                dacl.AddAce(aceEveryone);
                EditEveryone = true;
            }
            if (!EditSelf)
            {
                IADsAccessControlEntry aceSelf = new ActiveDs.AccessControlEntry();
                aceSelf.AccessMask = (int)ADS_RIGHTS_ENUM.ADS_RIGHT_DS_CONTROL_ACCESS;
                aceSelf.AceType = WorkFlag;
                aceSelf.AceFlags = 0;
                aceSelf.Flags = (int)ADS_FLAGTYPE_ENUM.ADS_FLAG_OBJECT_TYPE_PRESENT;
                aceSelf.ObjectType = CHANGE_PASSWORD_GUID;
                aceSelf.Trustee = NameSelf;
                dacl.AddAce(aceSelf);
                EditSelf = true;
            }

            if (EditEveryone && EditSelf)
            {
                _entry.Properties["ntSecurityDescriptor"].Value = secDes;
                _entry.CommitChanges();
            }
        }

        private void SetPrimaryGroup(string distinguishedName)
        {
            //TODO: shuold be changed to support credential
            DirectoryEntry group = new DirectoryEntry("LDAP://" + distinguishedName);
            group.RefreshCache(new string[] { "primaryGroupToken" });
            _entry.Properties["primaryGroupID"].Value = group.Properties["primaryGroupToken"].Value;
        }

        private void SetGroup(object[] Value)
        {
            int newScope = Int32.Parse(Value[0].ToString());
            int newType = Int32.Parse(Value[1].ToString());
            int oldScope = GetGroupScope();
            int oldType = GetGroupType();

            int GroupType = (int)_entry.Properties["groupType"].Value;

            if (newScope != oldScope)
            {
                GroupType &= ~GetADSGroupType(oldScope);
                GroupType |= GetADSGroupType(newScope);
            }
            if (newType != oldType)
            {
                if (newType == 0)
                {
                    GroupType |= (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_SECURITY_ENABLED;
                }
                else if (newType == 1)
                {
                    GroupType &= ~(int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_SECURITY_ENABLED;
                }
                else
                {
                    throw new ArgumentOutOfRangeException();
                }
            }
            _entry.Properties["groupType"].Value = GroupType;
        }

        private int GetADSGroupType(int scope)
        {
            switch (scope)
            {
                case 0:
                    return (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_LOCAL_GROUP;
                case 1:
                    return (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_GLOBAL_GROUP;
                case 2:
                    return (int)ADS_GROUP_TYPE_ENUM.ADS_GROUP_TYPE_UNIVERSAL_GROUP;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private byte[] ConvertToOctetString(object value)
        {
            object[] list = null;

            Type valueType = value.GetType();

            if (valueType == typeof(ArrayList))
            {
                list = (value as ArrayList).ToArray();
            }
            else
            {
                list = value as object[];
            }


            byte[] result = new byte[list.Length];
            for (int i = 0; i < list.Length; i++)
            {
                string listValue = list[i].ToString();

                result[i] = Convert.ToByte(listValue, listValue.StartsWith("0x") ? 16 : 10);
            }

            return result;
        }

        #endregion

        private string GetWellKnownAccountName(WellKnownSidType knownSid)
        {
            SecurityIdentifier identifier = new SecurityIdentifier(knownSid, null);
            NTAccount account = (NTAccount)identifier.Translate(typeof(NTAccount));
            return account.Value;
        }

        private string GetPropertySyntax(string PropertyName)
        {
            string result = "";

            using (DirectoryEntry entrySchema = _entry.SchemaEntry)
            {
                using (DirectoryEntry schemaClass = entrySchema.Parent)
                {
                    IADsClass adClass = entrySchema.NativeObject as IADsClass;
                    IADsContainer adContainer = schemaClass.NativeObject as IADsContainer;

                    IADsProperty adProperty = (IADsProperty)adContainer.GetObject("Property", PropertyName);
                    result = adProperty.Syntax;
                }
            }
            return result;
        }

        public ActiveDirectoryEntry GetActiveDirectoryEntry()
        {
            string classType = "";

            switch (_entryClass)
            {
                case DirectoryEntryClass.User:
                    classType = "User";
                    break;
                case DirectoryEntryClass.Group:
                    switch (GetGroupType())
                    {
                        case 0:
                            classType = "Security Group";
                            break;
                        case 1:
                            classType = "Distribution Group";
                            break;
                        default:
                            break;
                    }
                    switch (GetGroupScope())
                    {
                        case 0:
                            classType += " - Domain Local";
                            break;
                        case 1:
                            classType += " - Global";
                            break;
                        case 2:
                            classType += " - Universal";
                            break;
                        default:
                            break;
                    }
                    break;
                case DirectoryEntryClass.Contact:
                    classType = "Contact";
                    break;
                case DirectoryEntryClass.Computer:
                    classType = "Computer";
                    break;
                case DirectoryEntryClass.OrganizationalUnit:
                    classType = "Organizational Unit";
                    break;
                case DirectoryEntryClass.Volume:
                    classType = "Shared Folder";
                    break;
                case DirectoryEntryClass.Printer:
                    classType = "Printer";
                    break;
                case DirectoryEntryClass.msMQRecipient:
                    classType = "MSMQ Queue Alias";
                    break;
                case DirectoryEntryClass.inetOrgPerson:
                    classType = "InetOrgPerson";
                    break;
                case DirectoryEntryClass.Unkown:
                default:
                    classType = _entryClassName;
                    break;
            }

            return new ActiveDirectoryEntry(_entry, classType);
        }
    }
}
